package cn.renxuan.http.aliyun;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import cn.renxuan.http.HttpUtil.DateUtil;
import cn.renxuan.http.constant.Constants;
import cn.renxuan.http.constant.HttpHeader;
import cn.renxuan.http.constant.aliyun.SystemHeader;

public class SignUtil {

    public static Map<String, String> initialBasicHeader(String path, String appKey, String appSecret, String method,
            Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys,
            List<String> signHeaderPrefixList) {

        if (headers == null) {
            headers = new HashMap<>();
        }

        headers.put(HttpHeader.HTTP_HEADER_ACCEPT, "*/*");
        // Timestamp
        headers.put(SystemHeader.X_CA_TIMESTAMP, String.valueOf(DateUtil.convertDateTimeLong(new Date())));
        // Anti-replay
        headers.put(SystemHeader.X_CA_NONCE, UUID.randomUUID().toString());
        headers.put(SystemHeader.X_CA_KEY, appKey);
        headers.put(SystemHeader.X_CA_SIGNATURE,
                SignUtil.sign(path, method, appSecret, headers, querys, bodys, signHeaderPrefixList));

        return headers;
    }

    /*
     * 
     */
    public static String sign(String path, String method, String secret,
            Map<String, String> headers,
            Map<String, String> querys,
            Map<String, String> bodys,
            List<String> signHeaderPrefixList) {
        try {
            // Mac mac = Mac.getInstance("HmacSHA256");
            // SecretKeySpec secretKey = new
            // SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            // mac.init(secretKey);

            // String signStr = buildStringToSign(path, method, headers, querys, bodys,
            // signHeaderPrefixList);
            // byte[] hash = mac.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
            // return Base64.getEncoder().encodeToString(hash);

            Mac hmacSha256 = Mac.getInstance("HmacSHA256");
            byte[] appSecretBytes = secret.getBytes(Charset.forName("UTF-8"));
            hmacSha256.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA256"));
            String stringToSign = buildStringToSign(path, method, headers, querys, bodys, signHeaderPrefixList);
            byte[] md5Result = hmacSha256.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
            String signature = Base64.getEncoder().encodeToString(md5Result);
            return signature;
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate signature", e);
        }
    }

    /*
     * 
     */
    private static String buildStringToSign(String path, String method,
            Map<String, String> headers,
            Map<String, String> querys,
            Map<String, String> bodys,
            List<String> signHeaderPrefixList) {
        StringBuilder sb = new StringBuilder();

        sb.append(method.toUpperCase()).append(Constants.LF);
        if (headers.containsKey(HttpHeader.HTTP_HEADER_ACCEPT) && headers.get(HttpHeader.HTTP_HEADER_ACCEPT) != null) {
            sb.append(headers.get(HttpHeader.HTTP_HEADER_ACCEPT));
        }
        sb.append(Constants.LF);
        if (headers.containsKey(HttpHeader.HTTP_HEADER_CONTENT_MD5)
                && headers.get(HttpHeader.HTTP_HEADER_CONTENT_MD5) != null) {
            sb.append(headers.get(HttpHeader.HTTP_HEADER_CONTENT_MD5));
        }
        sb.append(Constants.LF);
        if (headers.containsKey(HttpHeader.HTTP_HEADER_CONTENT_TYPE)
                && headers.get(HttpHeader.HTTP_HEADER_CONTENT_TYPE) != null) {
            sb.append(headers.get(HttpHeader.HTTP_HEADER_CONTENT_TYPE));
        }
        sb.append(Constants.LF);
        if (headers.containsKey(HttpHeader.HTTP_HEADER_DATE) && headers.get(HttpHeader.HTTP_HEADER_DATE) != null) {
            sb.append(headers.get(HttpHeader.HTTP_HEADER_DATE));
        }
        sb.append(Constants.LF);
        sb.append(buildHeaders(headers, signHeaderPrefixList));
        sb.append(buildResource(path, querys, bodys));

        return sb.toString();
    }

    /*
     * 
     */
    private static String buildResource(String path, Map<String, String> querys, Map<String, String> bodys) {
        StringBuilder sb = new StringBuilder();
        if (path != null) {
            sb.append(path);
        }

        StringBuilder sbParam = new StringBuilder();
        Map<String, String> sortParams = new TreeMap<>();

        // query parameters
        if (querys != null && !querys.isEmpty()) {
            for (Map.Entry<String, String> param : querys.entrySet()) {
                if (param.getKey().length() > 0) {
                    sortParams.put(param.getKey(), param.getValue());
                }
            }
        }

        // body parameters
        if (bodys != null && !bodys.isEmpty()) {
            for (Map.Entry<String, String> param : bodys.entrySet()) {
                if (param.getKey().length() > 0) {
                    sortParams.put(param.getKey(), param.getValue());
                }
            }
        }

        // build parameter string
        for (Map.Entry<String, String> param : sortParams.entrySet()) {
            if (param.getKey().length() > 0) {
                if (sbParam.length() > 0) {
                    sbParam.append("&");
                }
                sbParam.append(param.getKey());
                if (param.getValue() != null && !param.getValue().isEmpty()) {
                    sbParam.append("=").append(param.getValue());
                }
            }
        }

        if (sbParam.length() > 0) {
            sb.append("?").append(sbParam);
        }

        return sb.toString();
    }

    private static String buildHeaders(Map<String, String> headers, List<String> signHeaderPrefixList) {
        StringBuilder sb = new StringBuilder();

        if (signHeaderPrefixList != null) {
            // Remove excluded headers
            signHeaderPrefixList.remove("X-Ca-Signature");
            signHeaderPrefixList.remove("X-Ca-Signature-Headers");
            signHeaderPrefixList.remove("Accept");
            signHeaderPrefixList.remove("Content-MD5");
            signHeaderPrefixList.remove("Content-Type");
            signHeaderPrefixList.remove("Date");
            Collections.sort(signHeaderPrefixList, String.CASE_INSENSITIVE_ORDER);
        }

        if (headers != null) {
            Map<String, String> sortedParams = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            sortedParams.putAll(headers);
            StringBuilder signHeadersStringBuilder = new StringBuilder();

            for (Map.Entry<String, String> param : sortedParams.entrySet()) {
                if (isHeaderToSign(param.getKey(), signHeaderPrefixList)) {
                    sb.append(param.getKey()).append(Constants.SPE2);
                    if (param.getValue() != null) {
                        sb.append(param.getValue());
                    }
                    sb.append(Constants.LF);

                    if (signHeadersStringBuilder.length() > 0) {
                        signHeadersStringBuilder.append(Constants.SPE1);
                    }
                    signHeadersStringBuilder.append(param.getKey());
                }
            }

            headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeadersStringBuilder.toString());
        }

        return sb.toString();
    }

    private static boolean isHeaderToSign(String headerName, List<String> signHeaderPrefixList) {
        if (headerName == null || headerName.isEmpty()) {
            return false;
        }

        if (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM)) {
            return true;
        }

        if (signHeaderPrefixList != null) {
            for (String signHeaderPrefix : signHeaderPrefixList) {
                if (headerName.startsWith(signHeaderPrefix)) {
                    return true;
                }
            }
        }

        return false;
    }

}
